package eval

import (
	"errors"
	"fmt"
	"showen7/ast"
	"showen7/inter"
	"showen7/num"
	"showen7/tok"
	"strings"
)

type TailCallResult struct {
	Proc inter.Object
	Args []inter.Object
	Env  *inter.Environment
}

func (t *TailCallResult) Type() inter.ObjectType {
	return -1 // Special type for tail call result
}

func (t *TailCallResult) Inspect() string {
	return "tail-call-result"
}

// EvalExpression evaluates an expression and returns the result
func EvalExpression(exp ast.Exp, env *inter.Environment) (inter.Object, error) {
	return evalExpressionWithTailCall(exp, env, false)
}

// EvalExpressionTrampoline is the main evaluation loop that handles tail calls
func EvalExpressionTrampoline(exp ast.Exp, env *inter.Environment) (inter.Object, error) {
	currentExp := exp
	currentEnv := env

	for {
		result, err := evalExpressionWithTailCall(currentExp, currentEnv, true)
		if err != nil {
			return nil, err
		}

		// Check if we got a tail call result
		if tailCall, ok := result.(*TailCallResult); ok {
			// Apply the procedure directly using applyObjectProcedure
			result, err := applyObjectProcedure(tailCall.Proc, tailCall.Args, tailCall.Env)
			if err != nil {
				return nil, err
			}
			return result, nil
		} else {
			// Not a tail call, return the result
			return result, nil
		}
	}
}

// evalExpressionWithTailCall is the internal evaluation function that supports tail call optimization
func evalExpressionWithTailCall(exp ast.Exp, env *inter.Environment, inTailPosition bool) (inter.Object, error) {
	switch exp := exp.(type) {
	case *ast.IdentifierExp:
		return evalIdentifier(exp, env)
	case *ast.BooleanExp:
		return evalBoolean(exp)
	case *ast.NumberExp:
		return evalNumber(exp)
	case *ast.StringExp:
		return evalString(exp)
	case *ast.CharacterExp:
		return evalCharacter(exp)
	case *ast.QuoteExp:
		return quoteExpressionToObject(exp.Value)
	/*case *ast.QuasiquoteExp:
		return evalQuasiquote(exp, env)
	case *ast.UnquoteExp:
		return evalUnquote(exp, env)
	case *ast.UnquoteSplicingExp:
		return evalUnquoteSplicing(exp, env)*/
	case *ast.ListExp:
		return evalListExpressionWithTailCall(exp, env, inTailPosition)
	case *ast.VectorExp:
		// Evaluate each element of the vector
		elements := make([]inter.Object, len(exp.Exps))
		for i, subExp := range exp.Exps {
			elem, err := evalExpressionWithTailCall(subExp, env, false) // Vector elements are not in tail position
			if err != nil {
				return nil, err
			}
			elements[i] = elem
		}
		return &inter.VectorObject{Elements: elements}, nil
	default:
		return nil, fmt.Errorf("unknown expression type: %T", exp)
	}
}

func applyObjectProcedure(procObj inter.Object, args []inter.Object, env *inter.Environment) (inter.Object, error) {
	switch proc := procObj.(type) {
	case *inter.Builtin:
		result := proc.Fn(args, env)
		if result.Type() == inter.ERROR_OBJ {
			return nil, errors.New(result.Inspect())
		}
		return result, nil
	case *inter.ContinuationObject:
		// Handle continuation inter.ects specially
		result, err := proc.Invoke(args)
		if err != nil {
			// If it's a ContinuationResultError, we propagate it up
			if _, ok := err.(*inter.ContinuationResultError); ok {
				return nil, err
			}
			return nil, err
		}
		return result, nil
	case *inter.Procedure:
		// Create a new environment for the procedure call
		newEnv := inter.NewEnclosedEnvironment(proc.Env)

		// Bind parameters to arguments
		if len(proc.Params) != len(args) {
			return nil, fmt.Errorf("wrong number of arguments to %s: expected %d, got %d",
				proc.Name, len(proc.Params), len(args))
		}

		for i, param := range proc.Params {
			newEnv.Set(param, args[i])
		}

		// Evaluate the procedure body
		var result inter.Object
		var err error
		for i, expr := range proc.Body {
			// Only the last expression is in tail position
			isTailPosition := (i == len(proc.Body)-1)
			if isTailPosition {
				// For the last expression, we might be able to do a tail call optimization
				// But for now, we'll evaluate it normally
				result, err = evalExpressionWithTailCall(expr, newEnv, false)
				// Check if we got a tail call result - this should not happen since we're passing false
				if tailCall, ok := result.(*TailCallResult); ok {
					// This should not happen, but if it does, apply it directly
					result, err = applyObjectProcedure(tailCall.Proc, tailCall.Args, tailCall.Env)
					if err != nil {
						return nil, err
					}
				}
			} else {
				result, err = evalExpressionWithTailCall(expr, newEnv, false)
				// Check if we got a tail call result - this should not happen since we're passing false
				if tailCall, ok := result.(*TailCallResult); ok {
					// This should not happen, but if it does, apply it directly
					result, err = applyObjectProcedure(tailCall.Proc, tailCall.Args, tailCall.Env)
					if err != nil {
						return nil, err
					}
				}
			}
			if err != nil {
				// If it's a ContinuationResultError, we propagate it up
				if _, ok := err.(*inter.ContinuationResultError); ok {
					return nil, err
				}
				return nil, err
			}
		}

		return result, nil
	case *inter.Lambda:
		// Create a new environment for the lambda call
		newEnv := inter.NewEnclosedEnvironment(proc.Env)

		// Handle parameters - check if we have a rest parameter
		var restParam string
		actualParams := proc.Parameters

		// Check if the last parameter is a rest parameter (marked with &rest:)
		if len(proc.Parameters) > 0 && strings.HasPrefix(proc.Parameters[len(proc.Parameters)-1], "&rest:") {
			// Extract the rest parameter name
			restParam = proc.Parameters[len(proc.Parameters)-1][6:] // Remove "&rest:" prefix
			// Remove the rest parameter marker from actual parameters
			actualParams = proc.Parameters[:len(proc.Parameters)-1]
		}

		// Bind regular parameters to arguments
		if len(actualParams) > len(args) {
			return nil, fmt.Errorf("wrong number of arguments: expected at least %d, got %d",
				len(actualParams), len(args))
		}

		// Bind regular parameters
		for i, param := range actualParams {
			newEnv.Set(param, args[i])
		}

		// Handle rest parameter if we have one
		if restParam != "" {
			// Create a list of remaining arguments
			var restArgs []inter.Object
			if len(args) > len(actualParams) {
				restArgs = args[len(actualParams):]
			} else {
				// No extra arguments, rest parameter gets empty list
				restArgs = []inter.Object{}
			}

			// Convert restArgs to a proper list
			var restList inter.Object = inter.EMPTY_LIST
			for i := len(restArgs) - 1; i >= 0; i-- {
				restList = &inter.PairObject{
					Car: restArgs[i],
					Cdr: restList,
				}
			}

			// Bind the rest parameter to the list of remaining arguments
			newEnv.Set(restParam, restList)
		} else if len(actualParams) != len(args) {
			// No rest parameter, so exact match is required
			return nil, fmt.Errorf("wrong number of arguments: expected %d, got %d",
				len(actualParams), len(args))
		}

		// Evaluate the lambda body with tail call support
		result, err := evalExpressionWithTailCall(proc.Body, newEnv, false)
		// Check if we got a tail call result - this should not happen since we're passing false
		if tailCall, ok := result.(*TailCallResult); ok {
			// This should not happen, but if it does, apply it directly
			result, err = applyObjectProcedure(tailCall.Proc, tailCall.Args, tailCall.Env)
			if err != nil {
				return nil, err
			}
		}
		// If it's a ContinuationResultError, we propagate it up
		if err != nil {
			if _, ok := err.(*inter.ContinuationResultError); ok {
				return nil, err
			}
			return nil, err // Propagate other errors properly
		}
		return result, err
	default:
		return nil, fmt.Errorf("attempt to apply non-procedure: %s", procObj.Inspect())
	}
}

func evalIdentifier(exp *ast.IdentifierExp, env *inter.Environment) (inter.Object, error) {
	if val, ok := env.Get(exp.Token.Literal.(string)); ok {
		return val, nil
	}

	// We don't handle builtins here, they should be in the environment
	return nil, fmt.Errorf("identifier not found: %s", exp.Token.Literal)
}
func evalBoolean(exp *ast.BooleanExp) (inter.Object, error) {
	return &inter.BooleanObject{Value: exp.Token.Literal.(bool)}, nil
}

func evalNumber(exp *ast.NumberExp) (inter.Object, error) {
	return &inter.NumberObject{Value: exp.Token.Literal.(num.Number)}, nil
}

func evalString(exp *ast.StringExp) (inter.Object, error) {
	return &inter.StringObject{Value: exp.Token.Literal.(string)}, nil
}

func evalCharacter(exp *ast.CharacterExp) (inter.Object, error) {
	return &inter.CharacterObject{Value: rune(exp.Token.Literal.(byte))}, nil
}

func evalListExpressionWithTailCall(expr *ast.ListExp, env *inter.Environment, inTailPosition bool) (inter.Object, error) {
	if len(expr.Exps) == 0 {
		return nil, fmt.Errorf("list cannot be empty")
	}
	if id, ok := expr.Exps[0].(*ast.IdentifierExp); ok {
		switch id.Token.Lexeme {
		case "define":
			return applyDefine(expr.Exps[1:], env)
		case "if":
			return applyIfWithTailCall(expr.Exps[1:], env, inTailPosition)
		case "lambda":
			return evalLambda(expr.Exps[1:], env)
		default:
			return evalProcedureWithTailCall(expr, env, inTailPosition)
		}
	}
	return evalApplicationWithTailCall(expr, env, inTailPosition)
}

func applyDefine(args []ast.Exp, env *inter.Environment) (inter.Object, error) {
	// Check if we have at least 2 arguments
	if len(args) < 2 {
		return nil, errors.New("define requires at least 2 arguments")
	}

	// Handle(define name value) form - first argument is a symbol
	if nameExpr, ok := args[0].(*ast.IdentifierExp); ok {
		// Evaluate the value expression
		if value, err := EvalExpression(args[1], env); err != nil {
			return nil, err
		} else {
			// In the environment, set the variable
			env.Set(nameExpr.Token.Lexeme, value)
			return inter.NULL_OBJECT, nil
		}
	}

	// Handle(define (name arg1 arg2 ...) body) form - first argument is a list
	if procDefExpr, ok := args[0].(*ast.ListExp); ok {
		if len(procDefExpr.Exps) < 1 {
			return nil, errors.New("invalid procedure definition in define")
		}

		// Check if the procedure name is a symbol
		procNameExpr, ok := procDefExpr.Exps[0].(*ast.IdentifierExp)
		if !ok {
			return nil, fmt.Errorf("procedure name %v must be an Identifier", procDefExpr.Exps[0])
		}

		// Extract parameters
		var params []string
		for i := 1; i < len(procDefExpr.Exps); i++ {
			paramExpr, ok := procDefExpr.Exps[i].(*ast.IdentifierExp)
			if !ok {
				return nil, fmt.Errorf("procedure name %v must be an Identifier", procDefExpr.Exps[0])
			}
			params = append(params, paramExpr.Token.Lexeme)
		}

		// Create a procedure inter.ect
		proc := &inter.Procedure{
			Name:   procNameExpr.Token.Lexeme,
			Params: params,
			Body:   args[1:], // The body consists of all expressions after the procedure definition
			Env:    env,
		}

		// In the environment, set the procedure
		env.Set(procNameExpr.Token.Lexeme, proc)
		return inter.NULL_OBJECT, nil
	}

	return nil, errors.New("define expects an identifer or a procedure definition as the first argument")
}

func evalProcedureWithTailCall(expr *ast.ListExp, env *inter.Environment, inTailPosition bool) (inter.Object, error) {
	fname := expr.Exps[0].(*ast.IdentifierExp).Token.Lexeme
	f, ok := env.Get(fname)
	if !ok {
		return nil, fmt.Errorf("undefined: %s", fname)
	}

	// Evaluate arguments
	args := make([]inter.Object, len(expr.Exps)-1)
	for i, argExpr := range expr.Exps[1:] {
		argObj, err := evalExpressionWithTailCall(argExpr, env, false) // Arguments are not in tail position
		if err != nil {
			return nil, err
		}
		args[i] = argObj
	}

	// If we're in a tail position, return a TailCallResult instead of directly applying
	if inTailPosition {
		return &TailCallResult{
			Proc: f,
			Args: args,
			Env:  env,
		}, nil
	}

	// Otherwise, apply the procedure normally
	return applyObjectProcedure(f, args, env)
}

func evalApplicationWithTailCall(expr *ast.ListExp, env *inter.Environment, inTailPosition bool) (inter.Object, error) {
	// Evaluate the first element to get the procedure
	procObj, err := evalExpressionWithTailCall(expr.Exps[0], env, false) // Procedure expression is not in tail position
	if err != nil {
		return nil, err
	}

	// Evaluate the arguments
	args := make([]inter.Object, len(expr.Exps)-1)
	for i, argExpr := range expr.Exps[1:] {
		argObj, err := evalExpressionWithTailCall(argExpr, env, false) // Arguments are not in tail position
		if err != nil {
			return nil, err
		}
		args[i] = argObj
	}

	// If we're in a tail position, return a TailCallResult instead of directly applying
	if inTailPosition {
		return &TailCallResult{
			Proc: procObj,
			Args: args,
			Env:  env,
		}, nil
	}

	// Apply the procedure
	return applyObjectProcedure(procObj, args, env)
}

func quoteExpressionToObject(exp ast.Exp) (inter.Object, error) {
	switch e := exp.(type) {
	case *ast.BooleanExp:
		return &inter.BooleanObject{Value: e.Token.Literal.(bool)}, nil
	case *ast.IdentifierExp:
		return &inter.SymbolObject{Value: e.Token.Lexeme}, nil
	case *ast.NumberExp:
		return &inter.NumberObject{Value: e.Token.Literal.(num.Number)}, nil
	case *ast.StringExp:
		return &inter.StringObject{Value: e.Token.Literal.(string)}, nil
	case *ast.CharacterExp:
		return &inter.CharacterObject{Value: rune(e.Token.Literal.(byte))}, nil
	case *ast.QuoteExp:
		// For nested quotes, we need to create a list with 'quote as the first element
		// and the inner quoted expression as the second element
		quoteSymbol := &inter.SymbolObject{Value: "quote"}
		quotedValue, err := quoteExpressionToObject(e.Value)
		if err != nil {
			return nil, err
		}

		// Create a pair representing (quote ...)
		pair := &inter.PairObject{}
		pair.Car = quoteSymbol
		pair.Cdr = &inter.PairObject{
			Car: quotedValue,
			Cdr: inter.EMPTY_LIST,
		}

		return pair, nil
	case *ast.ListExp:
		// Handle empty list as NULL_OBJECT
		if len(e.Exps) == 0 {
			return inter.EMPTY_LIST, nil
		}
		root := new(inter.PairObject)
		current := root
		elem, err := quoteExpressionToObject(e.Exps[0])
		if err != nil {
			return nil, err
		}
		current.Car = elem
		improper := false
		for i, subExp := range e.Exps[1:] {
			elem, err := quoteExpressionToObject(subExp)
			if err != nil {
				return nil, err
			}
			if elem.Type() == inter.SYMBOL_OBJ && elem.Inspect() == "." {
				improper = true
				continue
			} else {
				if !improper {
					o := new(inter.PairObject)
					o.Car = elem
					current.Cdr = o
					current = o
				} else {
					// Handle improper list - set the final cdr
					current.Cdr = elem
					//fmt.Println("last:", i)
					if i != len(e.Exps[1:])-1 {
						return nil, fmt.Errorf("the data behind . must be the last element")
					}
				}
			}
		}
		// For proper lists, the last cdr should be nil
		if !improper {
			current.Cdr = inter.EMPTY_LIST
		}
		return root, nil
	case *ast.VectorExp:
		elements := make([]inter.Object, len(e.Exps))
		for i, subExp := range e.Exps {
			elem, err := quoteExpressionToObject(subExp)
			if err != nil {
				return nil, err
			}
			elements[i] = elem
		}
		return &inter.VectorObject{Elements: elements}, nil
	default:
		return inter.NULL_OBJECT, nil
	}
}
func evalLambda(args []ast.Exp, env *inter.Environment) (inter.Object, error) {
	if len(args) < 2 {
		return nil, errors.New("lambda requires at least parameters and body")
	}

	// Handle parameter list - can be either:
	// 1. A proper list of identifiers: (param1 param2 ...)
	// 2. A single identifier that captures all arguments as a list: args
	var params []string
	var restParam string

	// Check if the first argument is a single identifier (for rest parameters)
	if identExp, ok := args[0].(*ast.IdentifierExp); ok {
		// This is the case where we capture all arguments as a list
		restParam = identExp.Token.Lexeme
		params = []string{} // No regular parameters
	} else if paramsList, ok := args[0].(*ast.ListExp); ok {
		// This is the normal case with a list of parameters
		// Extract parameter names
		for _, paramExpr := range paramsList.Exps {
			paramIdent, ok := paramExpr.(*ast.IdentifierExp)
			if !ok {
				return nil, errors.New("lambda parameters must be identifiers")
			}
			params = append(params, paramIdent.Token.Lexeme)
		}
	} else {
		return nil, errors.New("lambda parameters must be a list or identifier")
	}

	// Create a lambda inter.ect
	// For the body, we need to handle multiple expressions properly
	// If there's only one expression, use it directly
	// If there are multiple expressions, we need to wrap them in a begin expression
	var body ast.Exp
	if len(args[1:]) == 1 {
		body = args[1]
	} else {
		// Create a begin expression to evaluate multiple expressions in sequence
		beginExpr := &ast.ListExp{
			Exps: append([]ast.Exp{&ast.IdentifierExp{
				Token: tok.Token{Type: tok.IDENTIFIER, Lexeme: "begin"},
			}}, args[1:]...),
		}
		body = beginExpr
	}

	lambda := &inter.Lambda{
		Parameters: params,
		Body:       body,
		Env:        env,
	}

	// If we have a rest parameter, we need to handle it specially
	// We'll use a special naming convention to indicate this
	if restParam != "" {
		// Add a special marker to indicate we have a rest parameter
		lambda.Parameters = append(lambda.Parameters, "&rest:"+restParam)
	}

	return lambda, nil
}

func applyIfWithTailCall(args []ast.Exp, env *inter.Environment, inTailPosition bool) (inter.Object, error) {
	// Check if we have at least 2 arguments (test and consequent)
	// The alternative argument is optional
	if len(args) < 2 {
		return nil, errors.New("if requires at least 2 arguments")
	}

	// Evaluate the test condition
	test, err := evalExpressionWithTailCall(args[0], env, false) // Test is not in tail position
	if err != nil {
		return nil, err
	}

	// Check if test is true
	truthy := isTruthy(test)

	if truthy {
		// Evaluate the consequent in tail position if we're in a tail position
		return evalExpressionWithTailCall(args[1], env, inTailPosition)
	} else if len(args) >= 3 {
		// Evaluate the alternative in tail position if we're in a tail position
		return evalExpressionWithTailCall(args[2], env, inTailPosition)
	} else {
		// Return null if no alternative and test is false
		return inter.NULL_OBJECT, nil
	}
}

func isTruthy(o inter.Object) bool {
	if o.Type() == inter.BOOLEAN_OBJ {
		return o.(*inter.BooleanObject).Value
	}
	return true
}
